package br.com.hs.nfe.common.validation;

import br.com.hs.nfe.common.util.HSMessageConstants;
import br.com.hs.nfe.common.util.StringHelper;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.ValidatorHandler;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSResourceResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 * Classe responsável por efetuar validações no schema XML.
 * @author Ranlive Hrysyk
 */
public class HSSchemaValidator extends DefaultHandler {

    private static HSSchemaValidator hsdSchemaValidator = new HSSchemaValidator();
    private SchemaFactory schemaFactory;
    private XMLReader xmlReader;
    private Map<String, HSDSchema> map;
    private List<String> listaErros;

    /**
     * Construtor private para impedir o instânciamento de outra forma.
     */
    private HSSchemaValidator() {
        try {
            schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
            schemaFactory.setResourceResolver(new HSDLSResourceResolver());
            xmlReader = XMLReaderFactory.createXMLReader();
            map = new HashMap();
            listaErros = new ArrayList();
        } catch (SAXException e) {
            Logger.getLogger(HSSchemaValidator.class.getName()).log(Level.SEVERE, null, e);
        }
    }

    /**
     * Obtém a instância da classe HSSchemaValidator.
     */
    public static synchronized HSSchemaValidator getInstance() {
        return hsdSchemaValidator;
    }

    /**
     * Valida um XML pelo schema.
     * @param schemaType Tipo de schema.
     * @param versao Versão do schema.
     * @param xmlContent String do XML.
     * @return Lista de erros.
     */
    public List<String> validate(HSSchemaTypeEnum schemaType, String versao, String xmlContent) {
        try {
            listaErros.clear();
            HSDSchema schema = getHSDSchema(schemaType, versao);
            xmlReader.setContentHandler(schema.getValidatorHandler());
            xmlReader.parse(new InputSource(new StringReader(xmlContent)));
        } catch (MissingResourceException e) {
            listaErros.add(MessageFormat.format(HSMessageConstants.MSG_NOTA_FISCAL_VERSAO_INVALIDA, new Object[]{versao}));
        } catch (SAXException e) {
            Logger.getLogger(HSSchemaValidator.class.getName()).log(Level.SEVERE, null, e);
        } catch (IOException e) {
            Logger.getLogger(HSSchemaValidator.class.getName()).log(Level.SEVERE, null, e);
        }
        return listaErros.isEmpty() ? null : listaErros;
    }

    @Override
    public void error(SAXParseException e) throws SAXException {
        try {
            String errorMessage = e.getMessage();
            if (errorMessage.indexOf(':') > 0) {
                String errorMessageKey = errorMessage.substring(0, errorMessage.indexOf(':'));
                Object[] arguments = xmlSchemaMessages(errorMessage, errorMessageKey);
                String messagePattern = ResourceBundle.getBundle("br.com.hs.nfe.common.validation.schema_mensagens").getString(errorMessageKey);
                String message = MessageFormat.format(messagePattern, arguments);
                listaErros.add("[Nota Fiscal]: " + message);
            } else {
                listaErros.add("[Nota Fiscal]: " + errorMessage);
            }
        } catch (ParseException e1) {
            Logger.getLogger(HSSchemaValidator.class.getName()).log(Level.SEVERE, null, e1);
        }
    }

    /**
     * Obtém o array com as mensagens do schema.
     */
    private Object[] xmlSchemaMessages(String errorMessage, String errorMessageKey) throws ParseException {
        ResourceBundle resourceBundle = ResourceBundle.getBundle("com.sun.org.apache.xerces.internal.impl.msg.XMLSchemaMessages");
        if ((!StringHelper.isBlankOrNull(errorMessageKey)) && (resourceBundle.containsKey(errorMessageKey))) {
            String errorMessagePattern = resourceBundle.getString(errorMessageKey);
            MessageFormat messageFormat = new MessageFormat(errorMessagePattern);
            Object[] arguments = messageFormat.parse(errorMessage);
            return arguments;
        }
        return new Object[0];
    }

    private HSDSchema getHSDSchema(HSSchemaTypeEnum schemaType, String versao) throws SAXException, MissingResourceException {
        if (!map.containsKey(schemaType.name() + versao)) {
            String schemaPath = ResourceBundle.getBundle("br.com.hs.nfe.common.validation.schema_versoes").getString(schemaType.name() + '.' + versao);
            InputStream in = getClass().getClassLoader().getResourceAsStream(schemaPath);
            Schema schema = schemaFactory.newSchema(new StreamSource(in));
            ValidatorHandler validatorHandler = schema.newValidatorHandler();
            validatorHandler.setContentHandler(this);
            validatorHandler.setErrorHandler(this);
            map.put(schemaType.name() + versao, new HSDSchema(schema, validatorHandler));
        }
        return (HSDSchema) map.get(schemaType.name() + versao);
    }

    class HSDLSInput implements LSInput {

        private String publicId;
        private String systemId;
        private String baseURI;
        private InputStream byteStream;

        HSDLSInput(String publicId, String systemId, String baseURI) {
            this.publicId = publicId;
            this.systemId = systemId;
            this.baseURI = baseURI;
            byteStream = getClass().getClassLoader().getResourceAsStream("schema/src/" + systemId);
        }

        @Override
        public String getBaseURI() {
            return baseURI;
        }

        @Override
        public InputStream getByteStream() {
            return byteStream;
        }

        @Override
        public boolean getCertifiedText() {
            return false;
        }

        @Override
        public Reader getCharacterStream() {
            return null;
        }

        @Override
        public String getEncoding() {
            return null;
        }

        @Override
        public String getPublicId() {
            return publicId;
        }

        @Override
        public String getStringData() {
            return null;
        }

        @Override
        public String getSystemId() {
            return systemId;
        }

        @Override
        public void setBaseURI(String baseURI) {
            this.baseURI = baseURI;
        }

        @Override
        public void setByteStream(InputStream byteStream) {
            this.byteStream = byteStream;
        }

        @Override
        public void setCertifiedText(boolean certifiedText) {
        }

        @Override
        public void setCharacterStream(Reader characterStream) {
        }

        @Override
        public void setEncoding(String encoding) {
        }

        @Override
        public void setPublicId(String publicId) {
            this.publicId = publicId;
        }

        @Override
        public void setStringData(String stringData) {
        }

        @Override
        public void setSystemId(String systemId) {
            this.systemId = systemId;
        }
    }

    private class HSDLSResourceResolver implements LSResourceResolver {

        private final HSSchemaValidator schemaValidator;

        HSDLSResourceResolver() {
            schemaValidator = HSSchemaValidator.this;
        }

        @Override
        public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
            return schemaValidator.new HSDLSInput(publicId, systemId, baseURI);
        }
    }

    private class HSDSchema {

        private Schema schema;
        private ValidatorHandler validatorHandler;

        HSDSchema(Schema schema, ValidatorHandler validatorHandler) {
            this.schema = schema;
            this.validatorHandler = validatorHandler;
        }

        void setSchema(Schema schema) {
            this.schema = schema;
        }

        void setvalidatorHandler(ValidatorHandler validatorHandler) {
            this.validatorHandler = validatorHandler;
        }

        Schema getSchema() {
            return schema;
        }

        ValidatorHandler getValidatorHandler() {
            return validatorHandler;
        }
    }
}
